using System;
using System.Drawing;
using System.IO;
using OhioState.Math;

namespace OhioState.Graphics.Proxy
{
    public class VectorField : TexImage
    {
        private float[,] vectorX;
        private float[,] vectorY;

        public VectorField(String name, String filename)
            : base(name, new byte[] { }, 1, 1)
        {
            //ReadFile("C:\\Documents and Settings\\Oleg Mischenko\\Desktop\\psi2q.100");
            //ReadFileDat("C:\\Documents and Settings\\Oleg Mischenko\\Desktop\\vect.dat");
            ReadFileDat(filename);
            TexelFormat texelFormat = new TexelFormat();
            texelFormat.Format = ImageFormat.RGBA;
            texelFormat.Type = ByteFormat.BYTE;
            this.TexelType = texelFormat;
        }

        public VectorField(String name, String filename, bool isVect) : base(name, new byte[] {}, 1, 1)
        {
            ReadFileVect(filename);
            //this.Width = 1024;
            //this.Height = 1024;
            TexelFormat texelFormat = new TexelFormat();
            texelFormat.Format = ImageFormat.RGBA;
            texelFormat.Type = ByteFormat.BYTE;
            this.TexelType = texelFormat;
        }

        private void ReadFileDat(String filename)
        {
            FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
            StreamReader streamReader = new StreamReader(fileStream);

            streamReader.ReadLine();//V2
            String sizes = streamReader.ReadLine();//256 256
            this.Width = int.Parse(sizes.Split(new char[] {' '})[0]);
            this.Height = int.Parse(sizes.Split(new char[] { ' ' })[1]);

            vectorX = new float[this.Width, this.Height];
            vectorY = new float[this.Width, this.Height];

            String line = "";

            String[] separator = new String[] { " " };
            for (int i = 0; i < this.Width; i++)
            {
                for (int j = 0; j < this.Height; j++)
                {
                    line = streamReader.ReadLine();
                    String[] res = line.Split(separator, StringSplitOptions.None);
                    vectorX[i, j] = Convert.ToSingle(res[0]);
                    vectorY[i, j] = Convert.ToSingle(res[1]);
                }
            }

            streamReader.Close();
            fileStream.Close();

            //debug
            //ReadPngDataset();
            //ResizeDataset(1024, 1024);
            //SaveDataset(1024, 1024, true);
            //SaveDataset(1024, 1024, false);
            //for (int i = 0; i < 3; i++)
            //    BlurDataset();
            //end debug
        }

        private void ReadFileVect(String filename)
        {
            FileStream fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read);
            StreamReader streamReader = new StreamReader(fileStream);

            String data = streamReader.ReadLine();//the file consists of one long line
            streamReader.Close();
            fileStream.Close();

            String[] dataArray = data.Split(new char[] {' '});
            int width = int.Parse(dataArray[0]);
            int height = int.Parse(dataArray[1]);
            this.Width = width;
            this.Height = height;

            vectorX = new float[this.Width, this.Height];
            vectorY = new float[this.Width, this.Height];

            int row = 0;
            int column = 0;
            for (int i = 2; i < dataArray.Length - 1; i += 2)//skip last element, thus < -1
            {
                row = i / (this.Height * 2);
                column = (i - row * this.Width * 2) / 2;
                //normalize data first (make it from -1 to 1)
                if (row == this.Height || column == this.Width) continue;//hack!
                vectorX[row, column] = float.Parse(dataArray[i])/10;//normalize
                vectorY[row, column] = float.Parse(dataArray[i + 1])/10;//normalize
            }

            //debug!
            //ResizeDataset(1024, 1024);
            //for(int i = 0; i < 5; i++)
            //    BlurDataset();
        }

        private void ResizeDataset(int newW, int newH)
        {
            //find min-max
            float minVX = 1.0f, minVY = 1.0f;
            float maxVX = -1.0f, maxVY = -1.0f;
            for (int i = 0; i < this.Width; i++)
            {
                for (int j = 0; j < this.Height; j++)
                {
                    if (minVX > vectorX[i, j]) minVX = vectorX[i, j];
                    if (minVY > vectorY[i, j]) minVY = vectorY[i, j];
                    if (maxVX < vectorX[i, j]) maxVX = vectorX[i, j];
                    if (maxVY < vectorY[i, j]) maxVY = vectorY[i, j];
                }
            }

            //resize
            float[,] newVectorX = new float[newW, newH];
            float[,] newVectorY = new float[newW, newH];
            for (int i = 0; i < newW; i++)
            {
                for (int j = 0; j < newH; j++)
                {
                    int lookupX = (int)((i / (1.0f * newW)) * this.Width);
                    int lookupY = (int)((j / (1.0f * newH)) * this.Height);
                    newVectorX[i, j] = vectorX[lookupX, lookupY];
                    newVectorY[i, j] = vectorY[lookupX, lookupY];

                    //debug
                    //float newX = 0.1f;
                    //if(j > newH/2) newX = 0.9f;
                    //newVectorX[i, j] = newX;
                    //
                }
            }

            //set new data arrays and dimensions
            vectorX = newVectorX;
            vectorY = newVectorY;
            this.Width = newW;
            this.Height = newH;

            //knowing min-max, rescale values to a wider interval, namely [-1, 1]
            //for (int i = 0; i < newW; i++)
            //{
            //    for (int j = 0; j < newH; j++)
            //    {
            //        vectorX[i, j] = (vectorX[i, j] - minVX) / (maxVX - minVX) * 2.0f - 1.0f;
            //        vectorY[i, j] = (vectorY[i, j] - minVY) / (maxVY - minVY) * 2.0f - 1.0f;
            //    }
            //}
        }

        //
        // Few auxiliary methods for gaussian blur
        //
        private void BlurDataset()
        {
            int x = this.Width;
            int y = this.Height;
            float[,] newVectorX = new float[x, y];
            float[,] newVectorY = new float[x, y];
            for (int i = 0; i < x; i++)
            {
                for (int j = 0; j < y; j++)
                {
                    newVectorX[i, j] = GetGaussianSmoothedValue(i, j, true);
                    newVectorY[i, j] = GetGaussianSmoothedValue(i, j, false);
                }
            }
            vectorX = newVectorX;
            vectorY = newVectorY;
        }

        private double[] gaussianKernel = new double[] 
        {
            1.0/273.0, 4.0/273.0, 7.0/273.0, 4.0/273.0, 1.0/273.0,
	        4.0/273.0, 16.0/273.0, 26.0/273.0, 16.0/273.0, 4.0/273.0,
	        7.0/273.0, 26.0/273.0, 41.0/273.0, 26.0/273.0, 7.0/273.0,
	        4.0/273.0, 16.0/273.0, 26.0/273.0, 16.0/273.0, 4.0/273.0,
	        1.0/273.0, 4.0/273.0, 7.0/273.0, 4.0/273.0, 1.0/273.0
        };
        private int[][] gaussianKernelOffsets = new int[][]
        {
            new int[] {-2, -2}, new int[] {-1, -2}, new int[] {0, -2}, new int[]{1, -2}, new int[] {2, -2},
            new int[] {-2, -1}, new int[] {-1, -1}, new int[] {0, -1}, new int[]{1, -1}, new int[] {2, -1},
            new int[] {-2, 0}, new int[] {-1, 0}, new int[] {0, 0}, new int[]{1, 0}, new int[] {2, 0},
            new int[] {-2, 1}, new int[] {-1, 1}, new int[] {0, 1}, new int[]{1, 1}, new int[] {2, 1},
            new int[] {-2, 2}, new int[] {-1, 2}, new int[] {0, 2}, new int[]{1, 2}, new int[] {2, 2}
        };
        private float GetGaussianSmoothedValue(int i, int j, bool x)
        {
            float value = 0;
            for(int k = 0; k < 25; k++)
            {
                int lookupX = i + gaussianKernelOffsets[k][0];
                int lookupY = j + gaussianKernelOffsets[k][1];
                if (lookupX >= this.Width || lookupX < 0 || lookupY >= this.Height || lookupY < 0)
                    continue;
                if(x)
                    value += vectorX[lookupX, lookupY] * (float)gaussianKernel[k];
                else
                    value += vectorY[lookupX, lookupY] * (float)gaussianKernel[k];
            }

            return value;
        }
        //end of blur methods

        //method solely for debugging
        //save vx and vy to two png files
        private void SaveDataset(int x, int y, bool isX)
        {
            float[,] data = vectorX;
            if (!isX) data = vectorY;
            Bitmap bitmap = new Bitmap(x, y);
            for (int i = 0; i < x; i++)
            {
                for (int j = 0; j < y; j++)
                {
                    data[i, j] += 1.0f; //now not from [-1, 1], but [0, 2]
                    data[i, j] /= 2.0f; //now from [0, 1]
                    byte value = (byte)(data[i, j] * 255.0f);//don't forget to divide by 2.0 when reading back
                    bitmap.SetPixel(i, j, Color.FromArgb(255, value, value, value));
                }
            }
            bitmap.Save("output_" + (isX ? "x" : "y") + "_velocity.png", System.Drawing.Imaging.ImageFormat.Png);
        }

        //method solely for debugging
        //read two png files that contain vx and vy, and create a new dat file
        private void ReadPngDataset()
        {
            //read x and y separately
            String filenameX = "C:\\Documents and Settings\\Oleg Mischenko\\Desktop\\output_x_velocity.png";
            String filenameY = "C:\\Documents and Settings\\Oleg Mischenko\\Desktop\\output_y_velocity.png";
            Bitmap v_x = new Bitmap(filenameX);
            Bitmap v_y = new Bitmap(filenameY);
            vectorX = new float[1024, 1024];
            vectorY = new float[1024, 1024];
            for (int i = 0; i < v_x.Width; i++)
            {
                for (int j = 0; j < v_y.Height; j++)
                {
                    Color vx_c = v_x.GetPixel(i, j);
                    float valX = vx_c.R;
                    valX /= 255.0f;
                    valX *= 2.0f;
                    valX -= 1.0f;
                    vectorX[i, j] = valX;

                    Color vy_c = v_y.GetPixel(i, j);
                    float valY = vy_c.R;
                    valY /= 255.0f;
                    valY *= 2.0f;
                    valY -= 1.0f;
                    vectorY[i, j] = valY;
                }
            }

            //save it as vec file here
            FileStream filestream = new FileStream("C:\\Documents and Settings\\Oleg Mischenko\\Desktop\\vect_smooth.dat", FileMode.CreateNew);
            StreamWriter streamwriter = new StreamWriter(filestream);
            streamwriter.WriteLine("V2");
            streamwriter.WriteLine("1024 1024");
            for (int i = 0; i < 1024; i++)
            {
                for (int j = 0; j < 1024; j++)
                {
                    streamwriter.WriteLine(vectorX[i, j] + " " + vectorY[i, j]);
                }
            }
            streamwriter.Close();
            filestream.Close();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="texels"></param>
        public override void GetTexture(out byte[] texels)
        {
            texels = new byte[this.Height * this.Width * 4];

            int index = 0;
            for (int i = 0; i < this.Width; i++)
            {
                for (int j = 0; j < this.Height; j++)
                {
                    //if the values are negative, store the absolute value,
                    //set B to 0 for negative x, A to 0 for negative y
                    byte R = (byte)(System.Math.Abs(vectorX[i, j]) * 255.0f);
                    byte G = (byte)(System.Math.Abs(vectorY[i, j]) * 255.0f);
                    byte B = (byte)(vectorX[i, j] < 0 ? 255 : 0);
                    byte A = (byte)(vectorY[i, j] < 0 ? 255 : 0);

                    texels[index] = R;
                    texels[index + 1] = G;
                    texels[index + 2] = B;
                    texels[index + 3] = A;

                    index += 4;
                }
            }
        }
    }
}
